﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using EnvDTE;
using EnvDTE80;
using Microsoft.Win32;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Package;
using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
using ProcrastinationKit.Connectivity;
using ProcrastinationKit.Connectivity.Configuration;
using VSAddins;
using System.IO;
using System.Reflection;

namespace ProcrastinationKit
{
    internal sealed class ProcrastinationKit
    {
        public static PKConfiguration _configurationSection;
        public static Dictionary<string, IPKPluggable> _pluggins;
        private DTE2 dte;
        private VSInterface vsAddinContainer;
        private CommandInfo command;

        public ProcrastinationKit(DTE2 dte, VSInterface vsInterface)
        {
            this.dte = dte;
            this.vsAddinContainer = vsInterface;
            InterceptMessages.Initialize(this, dte);
        }

        public void Initialize()
        {
            
            _configurationSection = PKConfiguration.GetConfig();
            _pluggins = new Dictionary<string, IPKPluggable>();
            foreach (PKPluggin plugginConfig in _configurationSection.Pluggins)
            {
                IPKPluggable pluggin = LoadPluggin(plugginConfig);

                if (pluggin == null) continue;

                pluggin.Initialize(_configurationSection.Settings[plugginConfig.Name], dte);
                pluggin.OnSaveConfiguration += new SaveConfigurationHandler(pluggin_OnSaveConfiguration);

                //place commands
                AnchorCommandsToVS(pluggin.CommandsToAdd);

                _pluggins.Add(plugginConfig.Token, pluggin);
            }

            if (_configurationSection.Pluggins.Count > 0)
            {
                command = new CommandInfo("ProcrastinationKit.CleanTokens", new ActionNoArg(ClearTokenised))
                {
                    OnToolBar = true,
                    OnContextMenu = true,
                    ToolTip ="Clean Tokens in this document"
                };
                vsAddinContainer.AddCommand(command);
            }
        }

        private void ClearTokenised()
        {
            TextSelection objSel = (EnvDTE.TextSelection)( dte.ActiveDocument.Selection);
            EnvDTE.TextRanges textRanges = null;
            foreach (string srchPattern in _pluggins.Keys)
            {
                //Move to the beginning of the document so we can iterate 
                // over the whole thing.
                objSel.StartOfDocument(false);

                while (objSel.FindPattern("//" + srchPattern, 0, ref textRanges))
                {
                    //finding the length of the line, starting from the first /
                    int TextLenght = objSel.TopPoint.LineLength - objSel.TopPoint.LineCharOffset;

                    //deleting da line
                    //dont ask me why we have to remove 5 chars... I guess VS inserts something like \n\r
                    //or something
                    objSel.Delete(TextLenght - 5);
                } 
            }
            

           
            

           

           
        }

        private void AnchorCommandsToVS(List<CommandInfo> list)
        {
            foreach (CommandInfo command in list)
                vsAddinContainer.AddCommand(command);
        }

        public IPKPluggable LoadPluggin(PKPluggin pluggin)
        {
            if (!File.Exists(pluggin.Assembly))
                throw new FileNotFoundException();

            Assembly assembly = Assembly.LoadFrom(pluggin.Assembly);

            // Walk through each type in the assembly looking for our class

            foreach (Type type in assembly.GetTypes())
            {
                if (type.IsClass)
                {
                    if (type.FullName.EndsWith( pluggin.Class))
                    {
                        // create an instance of the object

                        //IPKPluggable obj = Activator.CreateInstance(type) as IPKPluggable;

                        // Dynamically Invoke the method

                        return type.InvokeMember("GetInstance", BindingFlags.InvokeMethod, null,type, new object[0]) as IPKPluggable;
                    }
                }
            }
            throw (new System.Exception("could not load object"));

        }

        private void pluggin_OnSaveConfiguration(object sender, PKConfigurationEventArgs e)
        {
            throw new NotImplementedException();
        }


        /// <summary>
        /// This handler is called when a message is -I hope so- writted
        /// </summary>
        public event EventHandler<PKMessageEventArgs> OnMessageTyped;

        public bool RegisterHyperLinkPrefix(string prefix)
        {
            // TODO failure to open key.  
            // Try not to open the registry for writing if you don't have to.  
            RegistryKey rk = Registry.ClassesRoot.OpenSubKey(c_protocolHandlerRegKey32);
            RegistryKey subKey = rk.OpenSubKey(prefix);
            bool ret = false;
            if (subKey == null)
            {
                rk.Close();
                rk = Registry.ClassesRoot.OpenSubKey(c_protocolHandlerRegKey32, true);
                subKey = rk.CreateSubKey(prefix);
                ret = true;
            }

            if (subKey != null)
                subKey.Close();
            rk.Close();
            return ret;
        }

        private static readonly string c_protocolHandlerRegKey32 = @"PROTOCOLS\Handler";

        private class InterceptMessages : IVsTextManagerEvents
        {
            public static void Initialize(ProcrastinationKit pKit, DTE2 dte)
            {
                if (sPKit != null)
                    throw new ApplicationException("HyperLink class must be a singleton");

                sPKit = pKit;
                s_dte = dte;
                IVsTextManager textManager = Services.GetIVsTextManager(dte);

                sIMessages = new InterceptMessages();
                sMsgTargets = new Dictionary<IVsTextView, PKCommandTarget>();

                // #RegisterForTextManagerEvents
                s_cookie = new ConnectionPointCookie(textManager, sIMessages, typeof(IVsTextManagerEvents));

                #region Register documents opened at start-up
                IVsRunningDocumentTable documentTable = Services.GetRunningDocumentTable(dte);
                IEnumRunningDocuments runningDocuments;
                documentTable.GetRunningDocumentsEnum(out runningDocuments);
                for (; ; )
                {
                    uint count = 1, fetched;
                    uint[] results = new uint[count];
                    runningDocuments.Next(count, results, out fetched);
                    if (fetched == 0)
                        break;

                    uint rdtFlags, readLocks, editLocks, itemId;
                    string documentTableName;
                    IVsHierarchy hierarchy;
                    IntPtr pUnkDocData;
                    documentTable.GetDocumentInfo(results[0],
                        out rdtFlags, out readLocks, out editLocks, out documentTableName,
                        out hierarchy, out itemId, out pUnkDocData);

                    object obj = null;
                    try { obj = Marshal.GetObjectForIUnknown(pUnkDocData); }
                    catch (SerializationException)
                    {
                        // #BugDuringRegistrationOfViewsOpenedAtStartup
                        continue;
                    }

                    IVsTextBuffer textBuffer = obj as IVsTextBuffer;
                    if (textBuffer != null)
                    {
                        IVsEnumTextViews views;
                        Services.GetIVsTextManager(dte).EnumViews(textBuffer, out views);

                        uint celt = 1;
                        IVsTextView[] rgelt = new IVsTextView[celt];
                        uint celtFetched = 1;
                        views.Next(celt, rgelt, ref celtFetched);

                        if (rgelt[0] != null)
                            // #RegisterDocumentsOpenedAtStartup
                            sIMessages.OnRegisterView(rgelt[0]);
                    }
                }
                #endregion

                InitializeAppDomainUnloadEvent();
            }
            private static void InitializeAppDomainUnloadEvent()
            {
                AppDomain.CurrentDomain.DomainUnload +=
                    delegate(object sender, EventArgs e)
                    {
                        // #UnregisterFromTextManager
                        s_cookie.Dispose();

                        foreach (PKCommandTarget target in sMsgTargets.Values)
                            target.Dispose();
                    };
            }

            public class PKCommandTarget : IOleCommandTarget, IVsIntellisenseHost, IDisposable
            {
                public PKCommandTarget(IVsTextView textView)
                {
                    m_textView = textView;
                    m_serviceProvider = null;// = serviceProvider;
                    // #AddCommandFilter
                    m_textView.AddCommandFilter(this, out m_nextCommandTarget);
                }

                #region IVsIntellisenseHost Members
                public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, OLECMDTEXT[] pCmdText)
                {
                    throw new Exception("The method or operation is not implemented.");
                }
                public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, ref object pvaIn, ref object pvaOut)
                {
                    throw new Exception("The method or operation is not implemented.");
                }
                public int GetHostFlags(out uint pdwFlags)
                {
                    // TODO: determine correct flags
                    pdwFlags = (uint)(IntellisenseHostFlags.IHF_NOSEPARATESUBJECT | IntellisenseHostFlags.IHF_FORCECOMMITTOCONTEXT);
                    return VSConstants.S_OK;
                }
                public int GetContextBuffer(out IVsTextLines ppCtxBuffer)
                {
                    return m_textView.GetBuffer(out ppCtxBuffer);
                }
                public int GetContextFocalPoint(TextSpan[] pSpan, ref int piLen)
                {
                    int hr = VSConstants.S_OK;
                    piLen = 0;

                    //// calculates the extent of the to-be-completed word
                    //int caretLine;
                    //int caretColumn;
                    //hr = m_textView.GetCaretPos(out caretLine, out caretColumn);
                    //if (VSConstants.Succeeded(hr))
                    //{
                    //    hr = m_textView.GetWordExtent(caretLine, caretColumn, 0, span);
                    //    piLen = span[0].iEndIndex - span[0].iStartIndex;
                    //}

                    return hr;

                }
                public int SetContextCaretPos(int iLine, int iIndex)
                {
                    return m_textView.SetCaretPos(iLine, iIndex);
                }
                public int GetContextCaretPos(out int piLine, out int piIndex)
                {
                    return m_textView.GetCaretPos(out piLine, out piIndex);
                }
                public int SetContextSelection(int iStartLine, int iStartIndex, int iEndLine, int iEndIndex)
                {
                    return m_textView.SetSelection(iStartLine, iStartIndex, iEndLine, iEndIndex);
                }
                public int GetContextSelection(TextSpan[] pSelectionSpan)
                {
                    return m_textView.GetSelectionSpan(pSelectionSpan);
                }
                public int GetSubjectText(out string pbstrSubjectText)
                {
                    pbstrSubjectText = null;
                    return VSConstants.E_NOTIMPL;
                }
                public int SetSubjectCaretPos(int iIndex)
                {
                    return VSConstants.E_NOTIMPL;
                }
                public int GetSubjectCaretPos(out int piIndex)
                {
                    piIndex = 0;
                    return VSConstants.E_NOTIMPL;
                }
                public int SetSubjectSelection(int iAnchorIndex, int iEndIndex)
                {
                    return VSConstants.E_NOTIMPL;
                }
                public int GetSubjectSelection(out int piAnchorIndex, out int piEndIndex)
                {
                    piAnchorIndex = 0;
                    piEndIndex = 0;
                    return VSConstants.E_NOTIMPL;
                }
                public int ReplaceSubjectTextSpan(int iStartIndex, int iEndIndex, string pszText)
                {
                    return VSConstants.E_NOTIMPL;
                }
                public int UpdateCompletionStatus(IVsCompletionSet pCompSet, uint dwFlags)
                {
                    throw new Exception("The method or operation is not implemented.");
                }
                public int UpdateTipWindow(IVsTipWindow pTipWindow, uint dwFlags)
                {
                    return VSConstants.S_OK;
                }
                public int HighlightMatchingBrace(uint dwFlags, uint cSpans, TextSpan[] rgBaseSpans)
                {
                    return VSConstants.S_OK;
                }
                public int BeforeCompletorCommit()
                {
                    return VSConstants.S_OK;
                }
                public int AfterCompletorCommit()
                {
                    return VSConstants.S_OK;
                }
                public int GetServiceProvider(out Microsoft.VisualStudio.OLE.Interop.IServiceProvider ppSP)
                {
                    ppSP = m_serviceProvider as Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
                    return VSConstants.S_OK;
                }
                public int GetHostWindow(out IntPtr hwnd)
                {
                    hwnd = m_textView.GetWindowHandle();
                    return VSConstants.S_OK;
                }
                public int GetContextLocation(int iPos, int iLen, int fUseCaretPosition, RECT[] prc, out int piTopX)
                {
                    //// unused
                    //prc = new RECT();
                    piTopX = 0;
                    return VSConstants.S_OK;

                }
                public int UpdateSmartTagWindow(IVsSmartTagTipWindow pSmartTagWnd, uint dwFlags)
                {
                    return VSConstants.S_OK;
                }
                public int GetSmartTagRect(RECT[] rcSmartTag)
                {
                    return VSConstants.E_NOTIMPL;
                }
                #endregion

                #region IOleCommandTarget Members
                public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
                {
                    if (pguidCmdGroup == typeof(VSConstants.VSStd2KCmdID).GUID &&
                        cCmds == (uint)VSConstants.VSStd2KCmdID.RETURN)
                    {
                        object o = Marshal.GetObjectForNativeVariant(pCmdText);
                        return VSConstants.S_OK;
                    }

                    return m_nextCommandTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); ;
                }
                public unsafe int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
                {
                    //if (pguidCmdGroup != typeof(VSConstants.VSStd2KCmdID).GUID ||
                    //    nCmdID != (uint)VSConstants.VSStd2KCmdID.RETURN)
                    //    return m_nextCommandTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
                    

                    if (pguidCmdGroup != typeof(VSConstants.VSStd2KCmdID).GUID )
                        return m_nextCommandTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
                    
                    if (nCmdID != (uint)VSConstants.VSStd2KCmdID.RETURN)
                        return m_nextCommandTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);

                    DTE2 dte = s_dte;

                    
                    TextSelection sel = (TextSelection)dte.ActiveDocument.Selection;
                    
                    //Gettin' the text from the whole line
                    EditPoint beginLine = sel.ActivePoint.CreateEditPoint();
                    beginLine.StartOfLine();
                    EditPoint endLine = sel.ActivePoint.CreateEditPoint();
                    endLine.EndOfLine();
                    string line = beginLine.GetText(endLine);

                    string comments = "";

                    switch (dte.ActiveDocument.Language)
                    {
                        case "CSharp":
                            comments = "//";
                            break;
                        case "Basic":
                            comments = "'";
                            break;
                        default:
                            dte.StatusBar.Text = "Languaje not supported by the ProcrastinationKit Addin";
                            break;
                    }
                    
                    
                    //do validation about msgs here!!!
                    string validToken = "";
                    int startPos = -1;
                    int row = beginLine.Line, col = 0;
                    foreach (string token in _pluggins.Keys)
                    {
                        if (line.Contains(comments + token ))
                        {
                            validToken = token;
                            startPos = line.IndexOf(comments + token);
                            break;
                        }
                    }
                    col = startPos;

                    if (string.IsNullOrEmpty(validToken))
                    {
                        m_nextCommandTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
                        return VSConstants.S_OK;
                    }

                    
                    int pos = line.IndexOf(":");
                    if (pos == -1)
                        pos = line.Length-1;
                    int dotPos = line.IndexOf(".");
                    string command = string.Empty;
                    if (dotPos != -1 && dotPos < pos &&  dotPos > startPos)
                        command = line.Substring(dotPos + 1, pos - dotPos).Trim();
                    
                    // #EventDispatch
                    PKMessageEventArgs args = new PKMessageEventArgs()
                    {
                        CompleteLine = line,
                        Token = validToken,
                        Command = command,
                        Arguments =  line.Substring(pos + 1).Trim(),
                        Document = dte.ActiveDocument,
                        Column = col,
                        Row = row,
                        Handled = false
                         
                    };
                    IPKPluggable pluggin = _pluggins[validToken];
                    if (pluggin != null)
                    {
                        try
                        {
                            pluggin.OnMessageReceived(this, args);
                        }
                        catch
                        {
                            
                        }
                    }
                    if (!args.Handled)
                        m_nextCommandTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
                    return VSConstants.S_OK;
                }
                #endregion

                #region IDisposable Members
                public void Dispose()
                {
                    if (m_textView != null)
                        // #RemoveCommandFilter
                        m_textView.RemoveCommandFilter(this);
                    m_textView = null;
                }
                #endregion

                private IOleCommandTarget m_nextCommandTarget;
                private IOleServiceProvider m_serviceProvider;
                private IVsTextView m_textView;
            }

            private static InterceptMessages sIMessages;
            private static ConnectionPointCookie s_cookie;
            private static Dictionary<IVsTextView, PKCommandTarget> sMsgTargets;
            private static DTE2 s_dte;
            private static ProcrastinationKit sPKit;

            #region IVsTextManagerEvents Members
            public void OnRegisterMarkerType(int iMarkerType)
            {
            }
            public void OnRegisterView(IVsTextView pView)
            {
                sMsgTargets[pView] = new PKCommandTarget(pView);
                //CompletionSet completionSet = new CompletionSet(null);
                //completionSet.Init(pView, new CommentDeclarations(), true);
            }
            public void OnUnregisterView(IVsTextView pView)
            {
                if (!sMsgTargets.ContainsKey(pView))
                    return;

                PKCommandTarget target = sMsgTargets[pView];
                sMsgTargets.Remove(pView);
                target.Dispose();
            }
            public void OnUserPreferencesChanged(
                VIEWPREFERENCES[] pViewPrefs, FRAMEPREFERENCES[] pFramePrefs,
                LANGPREFERENCES[] pLangPrefs, FONTCOLORPREFERENCES[] pColorPrefs)
            {
            }
            #endregion
        }

        private sealed class ConnectionPointCookie : IDisposable
        {
            #region Member Variables
            private Microsoft.VisualStudio.OLE.Interop.IConnectionPoint connectionPoint;
            private uint cookie;
            private Microsoft.VisualStudio.OLE.Interop.IConnectionPointContainer cpc;
            #endregion

            #region Constructor
            public ConnectionPointCookie(object source, object sink, Type eventInterface)
                : this(source, sink, eventInterface, true)
            {
            }
            public ConnectionPointCookie(object source, object sink, Type eventInterface, bool throwException)
            {
                Exception ex = null;
                if (source is Microsoft.VisualStudio.OLE.Interop.IConnectionPointContainer)
                {
                    cpc = (Microsoft.VisualStudio.OLE.Interop.IConnectionPointContainer)source;

                    try
                    {
                        Guid tmp = eventInterface.GUID;
                        cpc.FindConnectionPoint(ref tmp, out connectionPoint);
                    }
                    catch
                    {
                        connectionPoint = null;
                    }

                    if (connectionPoint == null)
                    {
                        ex = new ArgumentException(/* SR.GetString(SR.ConnectionPoint_SourceIF, eventInterface.Name)*/);
                    }
                    else if (sink == null || !eventInterface.IsInstanceOfType(sink))
                    {
                        ex = new InvalidCastException(/* SR.GetString(SR.ConnectionPoint_SinkIF)*/);
                    }
                    else
                    {
                        try
                        {
                            connectionPoint.Advise(sink, out cookie);
                        }
                        catch
                        {
                            cookie = 0;
                            connectionPoint = null;
                            ex = new Exception(/*SR.GetString(SR.ConnectionPoint_AdviseFailed, eventInterface.Name)*/);
                        }
                    }
                }
                else
                {
                    ex = new InvalidCastException(/*SR.ConnectionPoint_SourceNotICP)*/);
                }


                if (throwException && (connectionPoint == null || cookie == 0))
                {
                    if (ex == null)
                    {
                        throw new ArgumentException(/*SR.GetString(SR.ConnectionPoint_CouldNotCreate, eventInterface.Name)*/);
                    }
                    else
                    {
                        throw ex;
                    }
                }
            }
            #endregion

            #region Finalizer
            ~ConnectionPointCookie()
            {
#if DEBUG
                //System.Diagnostics.Debug.Assert(connectionPoint == null || cookie == 0,
                //    "We should never finalize an active connection point. (Interface = " +
                //    eventInterface.FullName +
                //    "), allocating code (see stack) is responsible for unhooking the ConnectionPoint by calling Disconnect.  Hookup Stack =\r\n" + callStack);
#endif

                // We must pass false here because chances are that if someone
                // forgot to Dispose this object, the IConnectionPoint is likely to be 
                // a disconnected RCW at this point (for example, we are far along in a 
                // VS shutdown scenario).  The result will be a memory leak, which is the 
                // expected result for an undisposed IDisposable object. [clovett] bug 369592.
                Dispose(false);
            }
            #endregion

            #region Private Members
            private void Dispose(bool disposing)
            {
                if (disposing)
                {
                    try
                    {
                        if (connectionPoint != null && cookie != 0)
                        {
                            connectionPoint.Unadvise(cookie);
                        }
                    }
                    finally
                    {
                        cookie = 0;
                        connectionPoint = null;
                        cpc = null;
                    }
                }
            }
            #endregion

            #region IDisposable Members
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }
            #endregion

        }
    }

  
}
